/*
 * Copyright (C) 2008 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.common.base.internal;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;

/**
 * Thread that finalizes referents. All references should implement {@code
 * com.google.common.base.FinalizableReference}.
 *
 * <p>While this class is public, we consider it to be *internal* and not part of our published API.
 * It is public so we can access it reflectively across class loaders in secure environments.
 *
 * <p>This class can't depend on other Guava code. If we were to load this class in the same class
 * loader as the rest of Guava, this thread would keep an indirect strong reference to the class
 * loader and prevent it from being garbage collected. This poses a problem for environments where
 * you want to throw away the class loader. For example, dynamically reloading a web application or
 * unloading an OSGi bundle.
 *
 * <p>{@code com.google.common.base.FinalizableReferenceQueue} loads this class in its own class
 * loader. That way, this class doesn't prevent the main class loader from getting garbage
 * collected, and this class can detect when the main class loader has been garbage collected and
 * stop itself.
 */
// no @ElementTypesAreNonNullByDefault for the reasons discussed above
public class Finalizer implements Runnable
{

    private static final Logger logger = Logger.getLogger(Finalizer.class.getName());

    /**
     * Name of FinalizableReference.class.
     */
    private static final String FINALIZABLE_REFERENCE = "com.google.common.base.FinalizableReference";

    /**
     * Starts the Finalizer thread. FinalizableReferenceQueue calls this method reflectively.
     *
     * @param finalizableReferenceClass FinalizableReference.class.
     * @param queue                     a reference queue that the thread will poll.
     * @param frqReference              a phantom reference to the FinalizableReferenceQueue, which will be queued
     *                                  either when the FinalizableReferenceQueue is no longer referenced anywhere, or when its
     *                                  close() method is called.
     */
    public static void startFinalizer(
            Class<?> finalizableReferenceClass,
            ReferenceQueue<Object> queue,
            PhantomReference<Object> frqReference)
    {
        /*
         * We use FinalizableReference.class for two things:
         *
         * 1) To invoke FinalizableReference.finalizeReferent()
         *
         * 2) To detect when FinalizableReference's class loader has to be garbage collected, at which
         * point, Finalizer can stop running
         */
        if (!finalizableReferenceClass.getName().equals(FINALIZABLE_REFERENCE))
        {
            throw new IllegalArgumentException("Expected " + FINALIZABLE_REFERENCE + ".");
        }

        Finalizer finalizer = new Finalizer(finalizableReferenceClass, queue, frqReference);
        String threadName = Finalizer.class.getName();
        Thread thread = null;
        if (bigThreadConstructor != null)
        {
            try
            {
                boolean inheritThreadLocals = false;
                long defaultStackSize = 0;
                thread =
                        bigThreadConstructor.newInstance(
                                (ThreadGroup) null, finalizer, threadName, defaultStackSize, inheritThreadLocals);
            }
            catch (Throwable t)
            {
                logger.log(
                        Level.INFO, "Failed to create a thread without inherited thread-local values", t);
            }
        }
        if (thread == null)
        {
            thread = new Thread((ThreadGroup) null, finalizer, threadName);
        }
        thread.setDaemon(true);

        try
        {
            if (inheritableThreadLocals != null)
            {
                inheritableThreadLocals.set(thread, null);
            }
        }
        catch (Throwable t)
        {
            logger.log(
                    Level.INFO,
                    "Failed to clear thread local values inherited by reference finalizer thread.",
                    t);
        }

        thread.start();
    }

    private final WeakReference<Class<?>> finalizableReferenceClassReference;
    private final PhantomReference<Object> frqReference;
    private final ReferenceQueue<Object> queue;

    // By preference, we will use the Thread constructor that has an `inheritThreadLocals` parameter.
    // But before Java 9, our only way not to inherit ThreadLocals is to zap them after the thread
    // is created, by accessing a private field.
    @CheckForNull
    private static final Constructor<Thread> bigThreadConstructor = getBigThreadConstructor();

    @CheckForNull
    private static final Field inheritableThreadLocals =
            (bigThreadConstructor == null) ? getInheritableThreadLocalsField() : null;

    /**
     * Constructs a new finalizer thread.
     */
    private Finalizer(
            Class<?> finalizableReferenceClass,
            ReferenceQueue<Object> queue,
            PhantomReference<Object> frqReference)
    {
        this.queue = queue;

        this.finalizableReferenceClassReference = new WeakReference<>(finalizableReferenceClass);

        // Keep track of the FRQ that started us so we know when to stop.
        this.frqReference = frqReference;
    }

    /**
     * Loops continuously, pulling references off the queue and cleaning them up.
     */
    @SuppressWarnings("InfiniteLoopStatement")
    @Override
    public void run()
    {
        while (true)
        {
            try
            {
                if (!cleanUp(queue.remove()))
                {
                    break;
                }
            }
            catch (InterruptedException e)
            {
                // ignore
            }
        }
    }

    /**
     * Cleans up a single reference. Catches and logs all throwables.
     *
     * @return true if the caller should continue, false if the associated FinalizableReferenceQueue
     * is no longer referenced.
     */
    private boolean cleanUp(Reference<?> reference)
    {
        Method finalizeReferentMethod = getFinalizeReferentMethod();
        if (finalizeReferentMethod == null)
        {
            return false;
        }
        do
        {
            /*
             * This is for the benefit of phantom references. Weak and soft references will have already
             * been cleared by this point.
             */
            reference.clear();

            if (reference == frqReference)
            {
                /*
                 * The client no longer has a reference to the FinalizableReferenceQueue. We can stop.
                 */
                return false;
            }

            try
            {
                finalizeReferentMethod.invoke(reference);
            }
            catch (Throwable t)
            {
                logger.log(Level.SEVERE, "Error cleaning up after reference.", t);
            }

            /*
             * Loop as long as we have references available so as not to waste CPU looking up the Method
             * over and over again.
             */
        }
        while ((reference = queue.poll()) != null);
        return true;
    }

    /**
     * Looks up FinalizableReference.finalizeReferent() method.
     */
    @CheckForNull
    private Method getFinalizeReferentMethod()
    {
        Class<?> finalizableReferenceClass = finalizableReferenceClassReference.get();
        if (finalizableReferenceClass == null)
        {
            /*
             * FinalizableReference's class loader was reclaimed. While there's a chance that other
             * finalizable references could be enqueued subsequently (at which point the class loader
             * would be resurrected by virtue of us having a strong reference to it), we should pretty
             * much just shut down and make sure we don't keep it alive any longer than necessary.
             */
            return null;
        }
        try
        {
            return finalizableReferenceClass.getMethod("finalizeReferent");
        }
        catch (NoSuchMethodException e)
        {
            throw new AssertionError(e);
        }
    }

    @CheckForNull
    private static Field getInheritableThreadLocalsField()
    {
        try
        {
            Field inheritableThreadLocals = Thread.class.getDeclaredField("inheritableThreadLocals");
            inheritableThreadLocals.setAccessible(true);
            return inheritableThreadLocals;
        }
        catch (Throwable t)
        {
            logger.log(
                    Level.INFO,
                    "Couldn't access Thread.inheritableThreadLocals. Reference finalizer threads will "
                            + "inherit thread local values.");
            return null;
        }
    }

    @CheckForNull
    private static Constructor<Thread> getBigThreadConstructor()
    {
        try
        {
            return Thread.class.getConstructor(
                    ThreadGroup.class, Runnable.class, String.class, long.class, boolean.class);
        }
        catch (Throwable t)
        {
            // Probably pre Java 9. We'll fall back to Thread.inheritableThreadLocals.
            return null;
        }
    }
}
